// 《围棋》作者版权所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package tar

import "strings"

// 格式代表tar存档格式。
// 
// 最初的tar格式是在Unix V7中引入的。
// 从那时起，有多种竞争格式试图对V7格式进行标准化或扩展，以克服其局限性。
// 最常见的格式是USTAR、PAX和GNU格式，
// 每种格式都有各自的优点和局限性。
// 
// 下表显示了每种格式的功能：
// 
// /| USTAR | PAX | GNU 
// /-------------+------------------+-----------------+-------------
// Name | 256B |无限|无限
// Linkname | 100B |无限|
// 大小| uint33 |无限| uint89 
// 模式| uint21 | uint21 | uint21 | uidt21 |无限| uint57 
// Uname/Gname | 32B |无限| 32B 
// ModTime 1243 |无限|访问时间|无限制的，无限制的，无限制的，无限制的，无限制的，无限制的，无限制的，无限制的，无限制的，无限制的，无限制的，无限制的文件|否|是|是
// 
// 表格的上半部分显示了标题字段，其中每个格式报告
// 每个字符串字段允许的最大字节数和
// 用于存储每个数字字段的整数类型
// （其中时间戳存储为自Unix纪元以来的秒数）。
// 
// 表格的下半部分显示了每种格式的特殊功能，如支持的字符串编码、支持亚秒时间戳、
// 或支持稀疏文件。
// 
// 编写器目前不支持稀疏文件。
type Format int

// 用于标识各种tar格式的常量。
const (
	// 故意对公共API隐藏常量的含义。
	_ Format = (1 << iota) / 4 // 0、0、1、2、4、8等的序列。。。

	// FormatUnknown表示格式未知。
	FormatUnknown

	// 标准化之前原始Unix V7 tar工具的格式。
	formatV7

	// FormatUSTAR表示POSIX中定义的USTAR头格式。1-1988. 
	// 
	// 虽然这种格式与大多数tar阅读器兼容，但
	// 这种格式有几个限制，不适合某些用途。
	// 最值得注意的是，它不能支持稀疏文件、大于8GiB的文件、大于256个字符的文件名和非ASCII文件名。
	// 
	// Reference:
	// http:
	FormatUSTAR

	// FormatPAX表示POSIX中定义的PAX头格式。1-2001. 
	// 
	// PAX通过在原始头前面写一个带有Typeflag TypeXHeader 
	// 的特殊文件来扩展USTAR。该文件包含一组键值
	// 记录，用于克服USTAR的缺点，此外还有
	// 提供时间戳的亚秒分辨率。
	// 
	// 一些较新的格式通过定义自己的密钥并为相关值赋予一定的语义含义，为PAX添加了自己的扩展。
	// 例如，PAX中的稀疏文件支持是使用GNU手册定义的键
	// 实现的（例如，“GNU.sparse.map”）。
	// 
	// Reference:
	// http:
	FormatPAX

	// FormatGNU表示GNU头格式。
	// 
	// GNU头格式比USTAR和PAX标准旧，
	// 与它们不兼容。GNU格式支持
	// 任意文件大小、任意编码和长度的文件名、
	// 稀疏文件和其他功能。
	// 
	// 建议选择PAX而不是GNU，除非目标
	// 应用程序只能解析GNU格式的归档文件。
	// 
	// Reference:
	// https:
	FormatGNU

	// Schily的tar格式，与USTAR不兼容。
	// 这不包括PAX格式的星型扩展；这些属于
	// PAX格式。
	formatSTAR

	formatMax
)

func (f Format) has(f2 Format) bool   { return f&f2 != 0 }
func (f *Format) mayBe(f2 Format)     { *f |= f2 }
func (f *Format) mayOnlyBe(f2 Format) { *f &= f2 }
func (f *Format) mustNotBe(f2 Format) { *f &^= f2 }

var formatNames = map[Format]string{
	formatV7: "V7", FormatUSTAR: "USTAR", FormatPAX: "PAX", FormatGNU: "GNU", formatSTAR: "STAR",
}

func (f Format) String() string {
	var ss []string
	for f2 := Format(1); f2 < formatMax; f2 <<= 1 {
		if f.has(f2) {
			ss = append(ss, formatNames[f2])
		}
	}
	switch len(ss) {
	case 0:
		return "<unknown>"
	case 1:
		return ss[0]
	default:
		return "(" + strings.Join(ss, " | ") + ")"
	}
}

// 用于识别各种格式的魔法。
const (
	magicGNU, versionGNU     = "ustar ", " \x00"
	magicUSTAR, versionUSTAR = "ustar\x00", "00"
	trailerSTAR              = "tar\x00"
)

// 各种焦油规格的尺寸常数。
const (
	blockSize  = 512 // tar流中每个块的大小
	nameSize   = 100 // USTAR格式名称字段的最大长度
	prefixSize = 155 // USTAR格式前缀字段的最大长度
)

// blockPadding计算将偏移量填充到
// 最近的块边缘所需的字节数，其中0<=n<blockSize。
func blockPadding(offset int64) (n int64) {
	return -offset & (blockSize - 1)
}

var zeroBlock block

type block [blockSize]byte

// 将块转换为任意数量的格式。
func (b *block) toV7() *headerV7       { return (*headerV7)(b) }
func (b *block) toGNU() *headerGNU     { return (*headerGNU)(b) }
func (b *block) toSTAR() *headerSTAR   { return (*headerSTAR)(b) }
func (b *block) toUSTAR() *headerUSTAR { return (*headerUSTAR)(b) }
func (b *block) toSparse() sparseArray { return sparseArray(b[:]) }

// GetFormat根据校验和检查块是否为有效的tar头。
// 然后它会尝试根据魔法值猜测特定的格式。
// 如果校验和失败，则返回FormatUnknown。
func (b *block) getFormat() Format {
	// 验证校验和。
	var p parser
	value := p.parseOctal(b.toV7().chksum())
	chksum1, chksum2 := b.computeChecksum()
	if p.err != nil || (value != chksum1 && value != chksum2) {
		return FormatUnknown
	}

	// 猜猜魔法值。
	magic := string(b.toUSTAR().magic())
	version := string(b.toUSTAR().version())
	trailer := string(b.toSTAR().trailer())
	switch {
	case magic == magicUSTAR && trailer == trailerSTAR:
		return formatSTAR
	case magic == magicUSTAR:
		return FormatUSTAR | FormatPAX
	case magic == magicGNU && version == versionGNU:
		return FormatGNU
	default:
		return formatV7
	}
}

// setFormat写入指定格式
// 所需的魔法值，然后相应地更新校验和。
func (b *block) setFormat(format Format) {
	// 设置魔法值。什么都不做。
	switch {
	case format.has(formatV7):
	case format.has(FormatGNU):
		copy(b.toGNU().magic(), magicGNU)
		copy(b.toGNU().version(), versionGNU)
	case format.has(formatSTAR):
		copy(b.toSTAR().magic(), magicUSTAR)
		copy(b.toSTAR().version(), versionUSTAR)
		copy(b.toSTAR().trailer(), trailerSTAR)
	case format.has(FormatUSTAR | FormatPAX):
		copy(b.toUSTAR().magic(), magicUSTAR)
		copy(b.toUSTAR().version(), versionUSTAR)
	default:
		panic("invalid format")
	}

	// 更新校验和。
	// 此字段的特殊之处在于它以空then空格结尾。
	var f formatter
	field := b.toV7().chksum()
	chksum, _ := b.computeChecksum() // 可能的值为256。。128776 
	f.formatOctal(field[:7], chksum) // 从不失败，因为128776<262143 
	field[7] = ' '
}

// ComputeTechEcksum计算头块的校验和。
// POSIX指定了无符号字节值的总和，但Sun tar使用了
// 有符号字节值。
// 我们计算并返回两者。
func (b *block) computeChecksum() (unsigned, signed int64) {
	for i, c := range b {
		if 148 <= i && i < 156 {
			c = ' ' // 将校验和字段本身视为所有空格。
		}
		unsigned += int64(c)
		signed += int64(int8(c))
	}
	return unsigned, signed
}

// Reset用全零清除块。
func (b *block) reset() {
	*b = block{}
}

type headerV7 [blockSize]byte

func (h *headerV7) name() []byte     { return h[000:][:100] }
func (h *headerV7) mode() []byte     { return h[100:][:8] }
func (h *headerV7) uid() []byte      { return h[108:][:8] }
func (h *headerV7) gid() []byte      { return h[116:][:8] }
func (h *headerV7) size() []byte     { return h[124:][:12] }
func (h *headerV7) modTime() []byte  { return h[136:][:12] }
func (h *headerV7) chksum() []byte   { return h[148:][:8] }
func (h *headerV7) typeFlag() []byte { return h[156:][:1] }
func (h *headerV7) linkName() []byte { return h[157:][:100] }

type headerGNU [blockSize]byte

func (h *headerGNU) v7() *headerV7       { return (*headerV7)(h) }
func (h *headerGNU) magic() []byte       { return h[257:][:6] }
func (h *headerGNU) version() []byte     { return h[263:][:2] }
func (h *headerGNU) userName() []byte    { return h[265:][:32] }
func (h *headerGNU) groupName() []byte   { return h[297:][:32] }
func (h *headerGNU) devMajor() []byte    { return h[329:][:8] }
func (h *headerGNU) devMinor() []byte    { return h[337:][:8] }
func (h *headerGNU) accessTime() []byte  { return h[345:][:12] }
func (h *headerGNU) changeTime() []byte  { return h[357:][:12] }
func (h *headerGNU) sparse() sparseArray { return sparseArray(h[386:][:24*4+1]) }
func (h *headerGNU) realSize() []byte    { return h[483:][:12] }

type headerSTAR [blockSize]byte

func (h *headerSTAR) v7() *headerV7      { return (*headerV7)(h) }
func (h *headerSTAR) magic() []byte      { return h[257:][:6] }
func (h *headerSTAR) version() []byte    { return h[263:][:2] }
func (h *headerSTAR) userName() []byte   { return h[265:][:32] }
func (h *headerSTAR) groupName() []byte  { return h[297:][:32] }
func (h *headerSTAR) devMajor() []byte   { return h[329:][:8] }
func (h *headerSTAR) devMinor() []byte   { return h[337:][:8] }
func (h *headerSTAR) prefix() []byte     { return h[345:][:131] }
func (h *headerSTAR) accessTime() []byte { return h[476:][:12] }
func (h *headerSTAR) changeTime() []byte { return h[488:][:12] }
func (h *headerSTAR) trailer() []byte    { return h[508:][:4] }

type headerUSTAR [blockSize]byte

func (h *headerUSTAR) v7() *headerV7     { return (*headerV7)(h) }
func (h *headerUSTAR) magic() []byte     { return h[257:][:6] }
func (h *headerUSTAR) version() []byte   { return h[263:][:2] }
func (h *headerUSTAR) userName() []byte  { return h[265:][:32] }
func (h *headerUSTAR) groupName() []byte { return h[297:][:32] }
func (h *headerUSTAR) devMajor() []byte  { return h[329:][:8] }
func (h *headerUSTAR) devMinor() []byte  { return h[337:][:8] }
func (h *headerUSTAR) prefix() []byte    { return h[345:][:155] }

type sparseArray []byte

func (s sparseArray) entry(i int) sparseElem { return sparseElem(s[i*24:]) }
func (s sparseArray) isExtended() []byte     { return s[24*s.maxEntries():][:1] }
func (s sparseArray) maxEntries() int        { return len(s) / 24 }

type sparseElem []byte

func (s sparseElem) offset() []byte { return s[00:][:12] }
func (s sparseElem) length() []byte { return s[12:][:12] }
